package com.hup.utils.swing.frame;

import com.hup.utils.commons.hardware.MouseUtil;
import com.hup.utils.commons.hardware.ScreenUtil;
import com.hup.utils.commons.start.LookThemeUtil;
import lombok.extern.log4j.Log4j2;

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

/**
 * 鼠标拖拽移动窗口
 * 代码缩减自 {@link WindowBoundsAdjustor}
 *
 * @author hugan
 * @date 2021/9/14
 * @apiNote 有边框的窗口时, 由于windows的窗口边框[左右下]会有半透明灰色带, 所以导致"窗口不能贴紧屏幕边缘"的现象,不是程序问题
 */
@SuppressWarnings("DuplicatedCode")
@Log4j2
public class WindowLocationAdjustor extends MouseAdapter {

    public static WindowLocationAdjustor bind(WindowLocationAdjustorCallback callback) {
        return new WindowLocationAdjustor(callback);
    }

    private final Window window;
    private final WindowLocationAdjustorCallback callback;
    private final boolean inScreen;
    private final int screenEdge;

    /**
     * 按下左键时的状态:鼠标坐标,窗口坐标...
     */
    private int mX0, mY0;
    private int wX0, wY0;

    private boolean canMove;
    private Insets screenCoor;
    /**
     * 按下左键时的状态
     */
    private boolean hadDragged;//用于过滤,只点击鼠标,无拖拽的事件
    private int releaseX, releaseY;
    private Boolean releaseOnTop, releaseOnLeft;

    private WindowLocationAdjustor(WindowLocationAdjustorCallback callback) {
        this.window = callback.getWindow();
        this.callback = callback;
        inScreen = callback.inScreen();
        screenEdge = callback.screenEdge();
        for (Component component : callback.getAdjustor()) {
            component.addMouseListener(this);
            component.addMouseMotionListener(this);
        }
    }

    @Override
    public void mousePressed(MouseEvent e) {
        if (!MouseUtil.isLeftButton(e)) return;
        //log.info("");
        mX0 = e.getXOnScreen();
        mY0 = e.getYOnScreen();
        wX0 = window.getX();
        wY0 = window.getY();
        hadDragged = false;
        canMove = true;
        screenCoor = ScreenUtil.getScreenUsableCoor();
        log.debug("screenCoor={}", screenCoor);
        e.consume();//阻止按下事件传递到子组件
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        if (canMove) {
            hadDragged = true;
            moveWindow(e);
        }
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        if (!MouseUtil.isLeftButton(e)) return;
        if (canMove) {
            if (hadDragged) {
                moveWindow(e);
                callback.afterMoved(releaseX, releaseY, releaseOnTop, releaseOnLeft);
                hadDragged = false;
                e.consume();
            }
            canMove = false;
            //阻止按下事件传递到子组件,必须与pressed.consume成对使用,否则按下效果不能被移除
        }
    }

    private void moveWindow(MouseEvent e) {
        int mX2 = e.getXOnScreen();
        int mY2 = e.getYOnScreen();
        //这里的坐标都是index,起始于0,最大值为[length-1]
        int x = wX0 + (mX2 - mX0);
        int y = wY0 + (mY2 - mY0);
        int rightX = x + window.getWidth() - 1;
        int rightY = y + window.getHeight() - 1;
        Boolean onTop = null, onLeft = null;
        //锁定在屏幕内
        if (inScreen) {
            if (x < 0) x = 0;
            else if (rightX > screenCoor.right) {
                x = screenCoor.right - window.getWidth() + 1;
                rightX = x + window.getWidth() - 1;
            }
            if (y < 0) y = 0;
            else if (rightY > screenCoor.bottom) {
                y = screenCoor.bottom - window.getHeight() + 1;
                rightY = y + window.getHeight() - 1;
            }
        }

        //磁吸效果
        if (x >= 0 && x <= screenEdge) {
            x = 0;
            onLeft = true;
        } else {
            if (rightX >= screenCoor.right - screenEdge && rightX <= screenCoor.right) {
                x = screenCoor.right - window.getWidth() + 1;
                onLeft = false;
            }
        }
        if (y >= 0 && y <= screenEdge) {
            y = 0;
            onTop = true;
        } else {
            if (rightY >= screenCoor.bottom - screenEdge && rightY <= screenCoor.bottom) {
                y = screenCoor.bottom - window.getHeight() + 1;
                onTop = false;
            }
        }

        window.setLocation(x, y);
        releaseX = x;
        releaseY = y;
        releaseOnTop = onTop;
        releaseOnLeft = onLeft;
        //log.info("[{},{}],{},{}", x, y, onTop, onLeft);//test
    }

    public interface WindowLocationAdjustorCallback {
        /**
         * 移动时,是否限制窗口在屏幕内
         *
         * @apiNote 只在绑定监听时赋值一次
         */
        default boolean inScreen() {
            return true;
        }

        /**
         * 移动时,边缘磁吸距离
         */
        default int screenEdge() {
            return (int) (8 * LookThemeUtil.getScreenScale());
        }

        /**
         * @return 目标窗口
         */
        Window getWindow();

        /**
         * 能被拖拽移动窗口的组件
         */
        Component[] getAdjustor();

        /**
         * @param x 移动后的位置
         * @param onTop true/false-在顶部/底部,null-没有在边缘;与 {@link #screenEdge()}相关
         */
        default void afterMoved(int x, int y, Boolean onTop, Boolean onLeft) { }
    }
}
